﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;

namespace System
{
	internal sealed class SystemNodePropertyAttribute : Attribute
	{
	}

	public sealed class NodeProperty
	{
		public string Name { get; set; }
		public string Description { get; set; }
		public bool IsSystemProperty { get; internal set; }
		public PropertyInfo PropertyInfo { get; set; }
		internal Node Node { get; set; }
		
		public object Value
		{
			get
			{
				return PropertyInfo.GetValue(Node, null);
			}
			set
			{
				PropertyInfo.SetValue(Node, value, null);
			}
		}
	}

	public abstract class NodeBase : ICloneable
	{
		#region ICloneable Members

		object ICloneable.Clone()
		{
			Type type = this.GetType();

			object obj = Activator.CreateInstance(type);

			PropertyInfo[] infos = type.GetProperties();
			foreach (PropertyInfo info in infos)
			{
				if (info.CanRead)
				{
					if (info.PropertyType.GetInterface(typeof(IList).FullName, true) != null)
					{
						IList targetList = info.GetValue(obj, null) as IList;
						IList sourceList = info.GetValue(this, null) as IList;

						foreach (object item in sourceList)
						{
							if (item is Node)
							{
								targetList.Add((item as ICloneable).Clone());
							}
						}
					}
					else if (info.CanWrite)
					{
						info.SetValue(obj, info.GetValue(this, null), null);
					}
				}
			}

			return obj;
		}

		#endregion
	}

	public abstract class Node : NodeBase
	{
		private const string NameField = "Name";
		private Dictionary<string, object> values = new Dictionary<string,object>();

		protected void SetValue(string key, object val, bool fireEvent)
		{
			if (!values.ContainsKey(key) || (values.ContainsKey(key) && ((values[key] == null && val != null) || !values[key].Equals(val))))
			{
				values[key] = val;

				if (fireEvent)
				{
					FirePropertyChangedEvent(this, key);
				}

				Unsaved = true;
			}
		}

		protected TValue GetValue<TValue>(string key, TValue defaultValue)
		{
			if (values.ContainsKey(key))
			{
				return (TValue)values[key];
			}

			return defaultValue;
		}

		[XmlAttribute(AttributeName = "id")]
		[SystemNodeProperty()]
		virtual public string ID
		{
			get;
			set;
		}

		[XmlAttribute(AttributeName = "name")]
		[SystemNodeProperty()]
		virtual public string Name
		{
			get
			{
				return GetValue<string>(NameField, null);
			}
			set
			{
				SetValue(NameField, value, true);
			}
		}

		internal bool Unsaved { get; set; }

		public bool IsUnsaved()
		{
			return Unsaved;
		}

		protected void FirePropertyChangedEvent(object sender, string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
			}

			Unsaved = true;
		}

		public void ClearEvents()
		{
			PropertyChanged = null;
		}

		public override string ToString()
		{
			if (string.IsNullOrEmpty(Name))
			{
				return string.Empty;
			}

			return Name;
		}

		public void Save(string fileName)
		{
			this.Unsaved = false;

			XmlSerializer formatter = new XmlSerializer(this.GetType());
			Stream stream = TitleContainer.OpenStream(fileName);
			formatter.Serialize(stream, this);
			stream.Close();
		}

		public static Node Load(Type type, string fileName)
		{
			try
			{
				XmlSerializer formatter = new XmlSerializer(type);
				Stream stream = TitleContainer.OpenStream(fileName);

				Node item = formatter.Deserialize(stream) as Node;
				item.Unsaved = false;

				stream.Close();

				return item;
			}
			catch (InvalidOperationException)
			{
				return null;
			}
			catch (Exception)
			{
				throw;
			}
		}

		public NodeProperty[] GetProperties()
		{
			List<NodeProperty> list = new List<NodeProperty>();

			Type type = this.GetType();
			PropertyInfo[] props = type.GetProperties(BindingFlags.NonPublic
					| BindingFlags.Public
					| BindingFlags.Instance
					| BindingFlags.Static);

			foreach (PropertyInfo prop in props)
			{
				if (prop.GetCustomAttributes(typeof(XmlAttributeAttribute), true).Length != 0)
				{
					NodeProperty nodeProp = new NodeProperty();
					nodeProp.Name = prop.Name;
					nodeProp.PropertyInfo = prop;
					nodeProp.IsSystemProperty = prop.GetCustomAttributes(typeof(SystemNodePropertyAttribute), true).Length > 0;

					object[] descs = prop.GetCustomAttributes(typeof(DescriptionAttribute), true);
					if (descs.Length != 0)
					{
						nodeProp.Description = (descs[0] as DescriptionAttribute).Description;
					}

					nodeProp.Node = this;

					list.Add(nodeProp);
				}
			}

			return list.ToArray();
		}

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}

	public abstract class Node<T> : Node
		where T : Node
	{
		static public T Load(string fileName)
		{
			return Node.Load(typeof(T), fileName) as T;
		}

		public T Clone()
		{
			return (T)(this as ICloneable).Clone();
		}
	}
}
